<?php
namespace Bundle\ModuleBundle;

use Bundle\ModuleBundle\Interfaces\ModuleInterface;
use Bundle\ModuleBundle\Interfaces\ModuleManagerInterface;
use Bundle\ModuleBundle\Interfaces\DataSourceInterface;

use Bundle\ConfigurationBundle\Configuration;

/**
 * Класс обработчик конфигов модулей
 */
class Module implements ModuleInterface, \Iterator
{
    /**
     * @var ModuleManagerInterface
     */
    private $_moduleManager;

    /**
     * @var Configuration
     */
    private $_configuration = null;

    /**
     * Массив разрешённых родителей
     * @var array
     */
    private $_parents = array();

    /**
     * Шаблон ассоциированный с модулем.
     * @var string
     * @remarks Стоит добавить динамики
     */
    private $_template = "";

    /**
     * Массив плагинов
     * @var array
     */
    private $_plugins = array();

    /**
     * Массив панелей
     * @var array
     */
    private $_panels = array();

    /**
     * Источник данных
     * @var DataSourceInterface
     */
    private $_datasource = null;
    
    /**
     * Использование базы данных
     * @var boolean
     */
    private $_usesDB = false;

    /**
     * Существует ли следующий элемент итерации
     * @var boolean
     */
    private $_hasNextIteration = false;
    
    /**
     * Данные для модулей
     * @var DataSet
     */
    public $data;

    public function __construct(ModuleManagerInterface $mm, $configuration, $name)
    {
        $this->_moduleManager = $mm;
        $this->_name = $name;

        //die(\var_dump($configuration));

        if(is_string($configuration))
        {
            try
            {
                if(file_exists($configuration))
                {
                    $this->_configuration = new Configuration($configuration);
                }
                else
                {
                    foreach($mm->getModulesSearchPath() as $path)
                    {
                        if(file_exists(realpath($path) . '\\' . $configuration))
                        {
                            $this->_configuration = new Configuration($configuration);
                            break;
                        }
                    }
                }
            }
            catch(\Exception $e)
            {
                throw new \Exception(sprintf("Exception while parsing configuration file `%s`.", $configuration),
                0, $e);
            }
        }
        elseif(is_object($configuration) && is_a($configuration, 'Bundle\ConfigurationBundle\Configuration'))
        {
            $this->_configuration = $configuration;
        }

        if($this->_configuration == null)
        {
            throw new \Exception("Configuration for module is not loaded.");
        }

        $this->preboot();
    }


    /**
     * Проверки файла конфигурации, инициализация структуры сущности, синхронизация её с базой данных
     * @return void
     */
    private function preboot()
    {
        if(!isset($this->_configuration->module))
        {
            throw new \Exception('Section `module` is not defined in configuration');
        }

        if(\array_key_exists('parents', $this->_configuration->module))
        {
            $this->_parents = $this->_configuration->module['parents'];
        }

        if(\array_key_exists('template', $this->_configuration->module))
        {
            $this->_template = $this->_configuration->module['template'];
        }

        if(\array_key_exists('plugins', $this->_configuration->module))
        {
            $this->processPlugins();
        }

        if(\array_key_exists('panels', $this->_configuration->module))
        {
            $this->processPanels();
        }

        if(\array_key_exists('routing', $this->_configuration->module))
        {
            $this->processRouting();
        }
            if(php_sapi_name() == 'cli')
        {
            return;
        }
        $this->processDatabase();
    }

    private function processDatabase()
    {
        $this->_usesDB = \array_key_exists('database', $this->_configuration->module);

        if(!$this->_usesDB)
        {
            foreach($this->_plugins as $plugin)
            {
                if(\count($plugin->getPersistantFields()) > 0)
                {
                    $this->_usesDB = true;
                    break;
                }
            }
            foreach($this->_panels as $panel)
            {
                if(!$this->_usesDB)
                {
                    foreach($panel as $plugin)
                    {
                        if(\count($plugin->getPersistantFields()) > 0)
                        {
                            $this->_usesDB = true;
                            break;
                        }
                    }
                }
                else
                {
                    break;
                }
            }
        }
    
        if($this->hasPersistantConfig())
        {
            $this->_processDatabaseAnnotation();
        }
        
    }

    private function processPlugins()
    {
        $pluginsConfig = $this->_configuration->module['plugins'];
        $this->_processPluginsAnnotation($pluginsConfig, $this->_plugins);
    }

    private function processPanels()
    {
        $panelsConfig = $this->_configuration->module['panels'];
        foreach($panelsConfig as $panel => $plugins)
        {
            $this->_panels[$panel] = array();
            $this->_processPluginsAnnotation($plugins, $this->_panels[$panel]);
        }
    }

    private function processRouting()
    {
        $this->_routing = $this->_configuration->module['routing'];
    }

    private function _processDatabaseAnnotation()
    {
        $dataSourceConfiguration = new Configuration(
            array(
                'database' => array
                            (
                                'table' => $this->getName(),
                                'datasource' => 'Bundle\\ModuleBundle\\DataSource\\DefaultDataSource',
                                'config' => array(),
                            )
            ));

        if(\array_key_exists('database', $this->_configuration->module))
        {
            $dataSourceConfiguration->merge(array('database' => $this->_configuration->module['database']));
        }

        $reflection = new \ReflectionClass($dataSourceConfiguration->database['datasource']);

        if($reflection->isSubclassOf('Bundle\\ModuleBundle\\DataSource\\DataSource') &&
                    !$reflection->isAbstract())
        {
            $this->_datasource = $reflection->newInstance($this->_moduleManager, $this, $dataSourceConfiguration);
        }
        else
        {
            throw new \Exception(sprintf('Can\'t create instance of `%s` datasource',
                $dataSourceConfiguration->database['datasource']));
        }

        foreach($this->_plugins as $name => $plugin)
        {
            foreach($plugin->getPersistantFields() as $fName => $field)
            {
                $methodTml = \sprintf("%s%s", $name, \ucfirst($fName));
                $this->_datasource->addField($methodTml, null, $field);
                $field->name = $methodTml;
            }
        }
        
        foreach($this->_panels as $panel => $items)
        {
            foreach($items as $name => $plugin)
            {
                foreach($plugin->getPersistantFields() as $fName => $field)
                {
                    $methodTml = \sprintf("%s%s%s", $name,\ucfirst($panel), \ucfirst($fName));
                    $this->_datasource->addField($methodTml, null, $field);
                    $field->name = $methodTml;
                }
            }
        }

        $this->_datasource->boot();
    }

    private function _processPluginsAnnotation($config, &$target)
    {
        if(!\is_array($config))
        {
            throw new \Exception("Plugins configuration is not correct.");
        }

        foreach($config as $name => $annotation)
        {
            if(!\array_key_exists('plugin', $annotation))
            {
                throw new \Exception(sprintf('Plugin type for `%s` is not defined.', $name));
            }
            if(!$this->_moduleManager->hasPlugin($annotation['plugin']))
            {
                throw new \Exception(sprintf('Plugin for type `%` with name `%s` was not found.', $annotation['plugin'], $name));
            }

            $description = $this->_moduleManager->getPlugin($annotation['plugin']);

            $configuration = new \Bundle\ConfigurationBundle\Configuration(
                        $description->getDefaultConfiguration());

            if(\array_key_exists('config', $annotation))
            {
                $configuration->merge($annotation['config']);
            }

            $plugin = $description->getInstance($this, $configuration);

            $plugin->boot();

            $target[$name] = $plugin;
        }
    }

    /**
     *
     */
    public function getTemplate()
    {
        return $this->_template;
    }
    /**
     * 
     */
    public function setTemplate($template)
    {
    	$this->_template = $template;
    }
    

    public function hasPersistantConfig()
    {
        return $this->_usesDB;
    }

    public function exportEntityScheme()
    {
        if($this->hasPersistantConfig())
        {
            return $this->_datasource->getEntityScheme();
        }
        return array();
    }

    public function processEntitiesData($data)
    {
        if($this->hasPersistantConfig())
        {
            $this->_datasource->processEntitiesData($data);
        }
        else
        {
            throw new \Exception('Module can\'t process entities data without datasource');
        }
    }

    public function exportFormScheme()
    {
        // TODO: Implement exportFormScheme() method.
    }

    public function processFormData($data)
    {
        // TODO: Implement processFormData() method.
    }

    public function getRouting()
    {
        return $this->_routing;
    }

    public function getDataSource()
    {
        return $this->_datasource;
    }

    public function getName()
    {
        return $this->_name;
    }

    public function boot()
    {
        if($this->hasPersistantConfig())
        {
        	$this->data = $this->_datasource->fetchData();
        }
    }

    public function getEntityClassName()
    {
        if($this->hasPersistantConfig())
        {
            return $this->_datasource->getEntityClassName();
        }
        throw new \Exception('Can\'t get entity class name for module without database use');
    }
    
    public function getRoute($alias)
    {
        return sprintf('%d_%s', $this->_moduleManager->getNodeId(), $alias);
    }

    /**
     * Преобразвание массива данных в массив сущностей
     * @param array $data
     */
    public function processArrayData($data)
    {
    	$processingPlugins = array();
    	foreach($this->_plugins as $name => $plugin)
    	{
    		foreach($plugin as $fieldName => $field)
    		{
    		    $arrayKey = $name . ucfirst($fieldName);
    			if(array_key_exists($arrayKey, $data))
    			{
    			    $field->setValue($data[$arrayKey]);
    			}
    		}
    		
    		$processingPlugins [] = $plugin;
    	}
    	foreach($this->_panels as $panelName => $panel)
    	{
    	    foreach($panel as $pluginName => $plugin)
    	    {
    	         foreach($plugin as $fieldName => $field)
    	         {
    	             $arrayKey = $panelName . ucfirst($pluginName) . ucfirst($fieldName);
                     if(array_key_exists($arrayKey, $data))
                     {
                          $field->setValue($data[$arrayKey]);
                     }    	         
    	         }
    	         $processingPlugins [] = $plugin;
    	    }
    	}
    	
    	foreach($processingPlugins as $plugin)
    	{
    	   $plugin->processFieldsData();
    	}
    	
    	$entities = $this->_datasource->collectEntities();

    	if(\array_key_exists('hidden', $data))
    	{
    	   $entities->setHidden((boolean) $data['hidden']);
    	}
    	
    	return $entities;
    }
    
    /**
     * Получение заголовков плагинов
     * @return array
     */
    public function getTitles()
    {
        $result = array();
        foreach($this->_plugins as $name => $plugin)
        {
        	$result[$name] = $plugin->getTitle();
        }        	
        return $result;
    }
    
    public function current()
    {
    	return current($this->_plugins);
    }
    
    public function key()
    {
    	return key($this->_plugins);
    }
    
    public function rewind()
    {
    	$this->_hasNextIteration = true;
        reset($this->_plugins);	
    }
    
    public function next()
    {
    	$this->_hasNextIteration = next($this->_plugins);
    } 
    
    public function valid()
    {
    	return $this->_hasNextIteration !== false;
    }
    
    
}
